// **************************************************************
// COPYRIGHT(c)2020, Xidian University
// All rights reserved.
//
// IP LIB INDEX :  
// IP Name      :      
// File name    : pingpang_ctrl.v
// Module name  : pingpang_ctrl
// Full name    :  
//
// Author       : Hbing
// Email        : 2629029232@qq.com
// Data         : 2020/9/12
// Version      : V 1.0 
// 
// Abstract     : 
// Called by    :  
// 
// Modification history
// -----------------------------------------------------------------
// 
// 
// 
// *****************************************************************
// `include "top_define.v"
// *******************
// TIMESCALE
// ******************* 
`timescale 1ns/1ps 

// *******************
// DESCRIPTION
// *******************
// 3*3交叉节点，但是为了防止阻塞，第三列交叉节点中的乒乓RAM翻倍，分别存port2,port3目的端口的数据帧
// 
// 
//*******************
//DEFINE(s)
//*******************
//`define UDLY 1    //Unit delay, for non-blocking assignments in sequential logic

//*******************
//DEFINE MODULE PORT
//*******************
module crossbar_ctl(
    //sysrem input/output
    input  wire         clk                 ,
    input  wire         rst_n               ,
    input wire   [9:0]  ram_2p_cfg_register,
    //with bus_master_tx
    input  wire [255:0] emac_data_in        ,
    (*mark_debug = "true"*) input  wire         emac_data_wren      ,
    input  wire [  5:0] rx_address_dpram    ,
    (*mark_debug = "true"*) input  wire         mul_indicate        ,
    (*mark_debug = "true"*) output wire         uni_buffer_val      ,
    (*mark_debug = "true"*) output wire         mul_buffer_val      ,
    //with MAC_IP
    input  wire         emac_rx_ready       ,
    output reg  [255:0] emac_data_final     ,
    output reg          emac_dval_final     ,
    output reg          emac_dsav_final     ,
    output reg          emac_sop_final      ,
    output reg          emac_eop_final      ,
    output reg  [  4:0] emac_mod_final      ,
    output reg  [  3:0] mac_dest_port_out   ,
    output wire         uni_out_busy        ,
    output wire         mul_out_busy        ,
    (*mark_debug = "true"*) input  wire         out_enable          ,
    output reg  [ 10:0] emac_len_final      ,//可以去掉
    //output reg  [  2:0] emac_pri_final      ,//可以去掉
    (*mark_debug = "true"*) output reg          read_finish

);
//*******************
//DEFINE LOCAL PARAMETER
//*******************
//parameter(s)
localparam IDLE       = 4'b0000;
localparam READ_HEAD  = 4'b0001;
localparam JUDGE      = 4'b0010;
localparam READ_FRAME = 4'b0100;
localparam FINISH     = 4'b1000;
//交叉节点乒乓RAM写入w256-d64
//6'b000000--队列号-帧长-目的端口列表
//6'b000001--SRAM_memory读出的数据
//*********************
//INNER SIGNAL DECLARATION
//*********************
//REGS
//状态机
(*mark_debug = "true"*) reg [3:0] c_state,n_state;
//乒乓RAM读使能、读地址、读反馈
reg [5:0] raddress;
reg       rden    ;
wire      ack     ;

//模块正在工作
reg       out_busy;
//指示输出的是单播还是多播帧
(*mark_debug = "true"*) reg out_mul_indicate;
//读数据计数
(*mark_debug = "true"*) reg [10:0] read_frame_cnt;
//读完一帧
// reg read_finish;
//将reading_frame提前拉高一个周期
wire reading_frame_pre;
//正在读帧
(*mark_debug = "true"*) reg reading_frame;
//读帧下降沿中间变量
reg reading_frame_d1;
//WIRES
//单播乒乓RAM写通道
wire [255:0] uni_rx_data_dpram_0;
wire [255:0] uni_rx_data_dpram_1;
wire         uni_rx_wren_dpram_0;
wire         uni_rx_wren_dpram_1;
//多播乒乓RAM写通道
wire [255:0] mul_rx_data_dpram_0;
wire [255:0] mul_rx_data_dpram_1;
wire         mul_rx_wren_dpram_0;
wire         mul_rx_wren_dpram_1;

//单多播乒乓RAM可读数据
wire uni_frm_val;
wire mul_frm_val;
//单播乒乓RAM读通道
wire [  5:0] uni_raddress   ;
wire         uni_rden       ;
wire         uni_ack        ;
wire [255:0] uni_q          ;
// wire [ 10:0] uni_length     ;
// wire         uni_slave_clr_a;
// wire         uni_slave_clr_b;
//多播乒乓RAM读通道
wire [  5:0] mul_raddress   ;
wire         mul_rden       ;
wire         mul_ack        ;
wire [255:0] mul_q          ;
// wire [ 10:0] mul_length     ;
// wire         mul_slave_clr_a;
// wire         mul_slave_clr_b;
//读通道归一
wire [255:0] q          ;
// wire [ 10:0] length     ;
// wire         slave_clr_a;  //一帧搬移完毕握手清空--是否有必要？
// wire         slave_clr_b;
//输出数据暂存
// reg  [255:0] emac_data_pre          ;
reg          emac_dval_pre          ;
// reg          emac_dsav_pre          ;
reg          emac_sop_pre           ;
reg          emac_eop_pre           ;
reg  [  4:0] emac_mod_pre           ;
reg  [  3:0] mac_dest_port_out_pre  ;
reg  [ 10:0] emac_len_pre           ;
reg  [  2:0] emac_pri_pre           ;
reg          emac_len_pre_val       ;
reg mul_indicate_can_change	    ;
reg out_mul_indicate_d1             ;

//****************将数据写入双口RAM中******************//
reg  emac_data_wren_ff1;
wire emac_data_wren_neg;  //一帧写完
reg mul_indicate_reg ;  //寄存标记
always @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
        emac_data_wren_ff1 <= 1'b0;
        mul_indicate_reg <= 1'b0;
    end
    else begin
        emac_data_wren_ff1 <= emac_data_wren;
        mul_indicate_reg <=mul_indicate;
    end
end

assign emac_data_wren_neg = (~emac_data_wren) & emac_data_wren_ff1;
//**********************UNI_RAM*********************//
reg  uni_ram_choose    ;  //选择写数据到乒乓RAM的A区或者B区

always @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
        uni_ram_choose <= 1'b0;
    end
    else if ((emac_data_wren_neg == 1'b1) && (mul_indicate_reg == 1'b0)) begin
        uni_ram_choose <= ~uni_ram_choose;
    end
    else begin
        uni_ram_choose <= uni_ram_choose;
    end
end

assign uni_rx_data_dpram_0 = ((uni_ram_choose == 1'b0) && (mul_indicate == 1'b0)) ? emac_data_in   : 256'b0;
assign uni_rx_wren_dpram_0 = ((uni_ram_choose == 1'b0) && (mul_indicate == 1'b0)) ? emac_data_wren : 1'b0  ;
assign uni_rx_data_dpram_1 = ((uni_ram_choose == 1'b1) && (mul_indicate == 1'b0)) ? emac_data_in   : 256'b0;
assign uni_rx_wren_dpram_1 = ((uni_ram_choose == 1'b1) && (mul_indicate == 1'b0)) ? emac_data_wren : 1'b0  ;
//**********************MUL_RAM*********************//
reg  mul_ram_choose    ;  //选择写数据到乒乓RAM的A区或者B区

always @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
        mul_ram_choose <= 1'b0;
    end
    else if ((emac_data_wren_neg == 1'b1) && (mul_indicate_reg == 1'b1)) begin
        mul_ram_choose <= ~mul_ram_choose;
    end
    else begin
        mul_ram_choose <= mul_ram_choose;
    end
end

assign mul_rx_data_dpram_0 = ((mul_ram_choose == 1'b0) && (mul_indicate == 1'b1)) ? emac_data_in   : 256'b0;
assign mul_rx_wren_dpram_0 = ((mul_ram_choose == 1'b0) && (mul_indicate == 1'b1)) ? emac_data_wren : 1'b0  ;
assign mul_rx_data_dpram_1 = ((mul_ram_choose == 1'b1) && (mul_indicate == 1'b1)) ? emac_data_in   : 256'b0;
assign mul_rx_wren_dpram_1 = ((mul_ram_choose == 1'b1) && (mul_indicate == 1'b1)) ? emac_data_wren : 1'b0  ;

//**************************读通道***********************//
assign uni_raddress = (out_mul_indicate == 1'b0) ? raddress : 6'b0 ;
assign mul_raddress = (out_mul_indicate == 1'b1) ? raddress : 6'b0 ;
assign uni_rden     = (out_mul_indicate == 1'b0) ? rden     : 1'b0 ;
assign mul_rden     = (out_mul_indicate == 1'b1) ? rden     : 1'b0 ;

assign q            = (out_mul_indicate_d1 == 1'b0) ? uni_q    : mul_q;
// assign length       = (out_mul_indicate == 1'b0) ? uni_length : mul_length;

assign uni_ack      = (out_mul_indicate == 1'b0) ? ack      : 1'b0;
assign mul_ack      = (out_mul_indicate == 1'b1) ? ack      : 1'b0;

// assign slave_clr_a  = (out_mul_indicate == 1'b0) ? uni_slave_clr_a : mul_slave_clr_a;
// assign slave_clr_b  = (out_mul_indicate == 1'b1) ? uni_slave_clr_b : mul_slave_clr_b;

assign uni_out_busy = (out_mul_indicate == 1'b0) ? out_busy : 1'b0;
assign mul_out_busy = (out_mul_indicate == 1'b1) ? out_busy : 1'b0;

//*********************
//INSTANTCE MODULE
//*********************
    pingpang_ctrl uni_pingpang_ctrl(
            .clk              (clk),
            .rst_n            (rst_n),
            .ram_2p_cfg_register(ram_2p_cfg_register),
            //write
            //.dpram_en         (1'b0/*dpram_en*/),
            //.dpram_num        (1'b0/*dpram_num*/),
            .rx_address_dpram (rx_address_dpram),
            .rx_data_dpram_0  (uni_rx_data_dpram_0),
            .rx_data_dpram_1  (uni_rx_data_dpram_1),
            .rx_wren_dpram_0  (uni_rx_wren_dpram_0),
            .rx_wren_dpram_1  (uni_rx_wren_dpram_1),
            .buffer_available (uni_buffer_val),
            //read
            .address          (uni_raddress),
            .rden             (uni_rden),
            .qout             (uni_q),
            .frame_available  (uni_frm_val),
            .ack              (uni_ack)
            // .length_o         (uni_length)
            // .slave_clr_a      (uni_slave_clr_a),
            // .slave_clr_b      (uni_slave_clr_b)
        );

    pingpang_ctrl mul_pingpang_ctrl(
            .clk              (clk),
            .rst_n            (rst_n),
            .ram_2p_cfg_register(ram_2p_cfg_register),
            //write
            //.dpram_en         (1'b0/*dpram_en*/),
            //.dpram_num        (1'b0/*dpram_num*/),
            .rx_address_dpram (rx_address_dpram),
            .rx_data_dpram_0  (mul_rx_data_dpram_0),
            .rx_data_dpram_1  (mul_rx_data_dpram_1),
            .rx_wren_dpram_0  (mul_rx_wren_dpram_0),
            .rx_wren_dpram_1  (mul_rx_wren_dpram_1),
            .buffer_available (mul_buffer_val),
            //read
            .address          (mul_raddress),
            .rden             (mul_rden),
            .qout             (mul_q),
            .frame_available  (mul_frm_val),
            .ack              (mul_ack)
            // .length_o         (mul_length)
            // .slave_clr_a      (mul_slave_clr_a),
            // .slave_clr_b      (mul_slave_clr_b)
        );

//*********************
//MAIN CORE
//********************* 
//状态机--从乒乓RAM读数据
always @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
        c_state <= IDLE;
    end
    else begin
        c_state <= n_state;
    end
end

always @(*)
begin
    case(c_state)
        IDLE:
        begin
            if (reading_frame == 1'b1) begin//上一帧没读完在IDLE等待，考虑长帧的情况。
                n_state = IDLE;
            end
            else if ((uni_frm_val == 1'b1) || (mul_frm_val == 1'b1)) begin//当前帧未读完时，uni_frm_val也为高，会导致读下一帧头信息读空；因此uni_frm_val应为下一帧的有效信号,或提前拉低
                n_state = READ_HEAD;
            end
            else begin
                n_state = IDLE;
            end
        end
        READ_HEAD:
        begin
            if ((emac_rx_ready) && (out_enable == 1'b1)) begin//收到下一级调度结果后开始读帧
                n_state = READ_FRAME;
            end
            else begin
                n_state = READ_HEAD;
            end
        end
        READ_FRAME:
        begin
            n_state = IDLE;
        end
        default:
        begin
            n_state = IDLE;
        end
    endcase
end

//reading_frame 从进入开始读帧状态机到读完结束
//  帧长-已读帧长<=32字节，即读最后一拍数据时拉低reading_frame，提前一个周期拉低
//  目的是处理最短帧情况下，提前进入READ_HEAD状态机，加快处理速度。
always @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
        reading_frame <= 1'b0;
    end
    else if (n_state == READ_FRAME) begin
        reading_frame <= 1'b1;
    end
    else if ((emac_len_pre - read_frame_cnt <= 11'd32)) begin
        reading_frame <= 1'b0;
    end
    else begin
        reading_frame <= reading_frame;
    end
end

//将reading_frame提前拉高一个周期，用于读地址的递增和读帧计数
assign reading_frame_pre = (n_state == READ_FRAME) || reading_frame;

//输出忙信号,给clumn_select用于状态跳转
//  表明该节点处有数据待发送
always @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
        out_busy <= 1'b0;
    end
    else if (n_state == READ_HEAD) begin
        out_busy <= 1'b1;
    end
    else if (c_state == READ_FRAME) begin
        out_busy <= 1'b0;
    end
    else begin
        out_busy <= out_busy;
    end
end

//输出数据帧单多播标识
always @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
        out_mul_indicate <= 1'b0;
    end
    else if (mul_indicate_can_change == 1'b1) begin
        if (mul_frm_val == 1'b1) begin
            out_mul_indicate <= 1'b1;
        end
        else if (uni_frm_val == 1'b1) begin
            out_mul_indicate <= 1'b0;
        end
        else begin
            out_mul_indicate <= out_mul_indicate;
        end
    end
    else begin
        out_mul_indicate <= out_mul_indicate;
    end
end

always @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
        out_mul_indicate_d1 <= 1'b0;
    end
    else begin
        out_mul_indicate_d1 <= out_mul_indicate;
    end
end

always @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
        mul_indicate_can_change <= 1'b1;
    end 
    else if ((c_state == IDLE) && (n_state == READ_HEAD)) begin//read 0 addr //high pri
        mul_indicate_can_change <= 1'b0;
    end
    else if ((emac_len_pre - read_frame_cnt <= 11'd32) && (emac_len_pre_val == 1'b1)) begin//read_finish
        mul_indicate_can_change <= 1'b1;
    end
    else begin
        mul_indicate_can_change <= mul_indicate_can_change;
    end
end

//给出乒乓RAM读使能、读地址
always @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
        raddress <= 6'b0;
        rden     <= 1'b0;
    end
    else if ((c_state == IDLE) && (n_state == READ_HEAD)) begin
        raddress <= 6'b0;
        rden     <= 1'b1;
    end
    else if (reading_frame_pre == 1'b1) begin
        raddress <= raddress + 6'd1;
        rden     <= 1'b1;
    end
    else begin
        raddress <= 6'b0;
        rden     <= 1'b0;
    end
end

//数据帧长度、优先级
always @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
        mac_dest_port_out_pre <= 4'b0;
        emac_len_pre    <= 11'b0;
        emac_pri_pre    <= 3'b0;
    end
    else if (n_state == READ_FRAME) begin
        mac_dest_port_out_pre <= q[3:0];
        emac_len_pre    <= q[14:4];
        emac_pri_pre    <= q[17:15];
    end
    else begin
        mac_dest_port_out_pre <= mac_dest_port_out_pre;
        emac_len_pre    <= emac_len_pre;
        emac_pri_pre    <= emac_pri_pre;
    end
end

//帧长有效信号
always @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
        emac_len_pre_val <= 1'b0;
    end
    else if (n_state == READ_FRAME) begin//与emac_len_pre同时到
        emac_len_pre_val <= 1'b1;
    end
    else if (read_finish == 1'b1) begin
        emac_len_pre_val <= 1'b0;
    end
    else begin
        emac_len_pre_val <= emac_len_pre_val;
    end
end

//读数据帧计数
always @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
        read_frame_cnt <= 11'b0;
    end
    else if (reading_frame_pre == 1'b1) begin
        read_frame_cnt <= read_frame_cnt + 11'd32;
    end
    else if (reading_frame_pre == 1'b0) begin
        read_frame_cnt <= 11'b0;
    end
    else begin
        read_frame_cnt <= read_frame_cnt;
    end
end

//产生结束标志,输出到列选择模块
//  帧长-已读帧长<=32字节，即读最后一拍数据时拉高read_finish（提前一个周期）
//  目的是提前让列选择模块回到IDLE，以提高最短帧调度速度
//                         ____
//  emac_len_pre_val    __|    |__
//                            ____
//  read_finish            __|    |__
//                         ________
//  emac_len_pre_val    __|        |__
//                                ____
//  read_finish                __|    |__
always @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
        read_finish <= 1'b0;
    end
    else if ((emac_len_pre - read_frame_cnt <= 11'd32) && (emac_len_pre_val == 1'b1)) begin
        read_finish <= 1'b1;
    end
    else begin
        read_finish <= 1'b0;
    end
end


always @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
        reading_frame_d1 <= 1'b0;
    end
    else begin
        reading_frame_d1 <= reading_frame;
    end
end
//一帧搬移完毕信号 reading_frame的下降沿
//  读最后一拍数据时拉高（提前一个周期ack）
assign ack = reading_frame_d1 & (~reading_frame);

//产生数据帧信号
    //dsav
always @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
        emac_dsav_final <= 1'b0;
    end
    else if (c_state == READ_FRAME) begin
        emac_dsav_final <= 1'b1;
    end
    else if (emac_eop_final == 1'b1) begin
        emac_dsav_final <= 1'b0;
    end
    else begin
        emac_dsav_final <= emac_dsav_final;
    end
end
    //sop 
always @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
        emac_sop_pre <= 1'b0;
    end
    else if ((c_state == READ_FRAME) && (read_frame_cnt == 11'd32)) begin
        emac_sop_pre <= 1'b1;
    end
    else begin
        emac_sop_pre <= 1'b0;
    end
end
    //data--组合赋值提速
//assign emac_data_pre = q;
    //dval 
always @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
        emac_dval_pre <= 1'b0;
    end
    else if ((c_state == READ_FRAME) && (read_frame_cnt == 11'd32)) begin
        emac_dval_pre <= 1'b1;
    end
    else if (read_frame_cnt == 11'b0) begin
        emac_dval_pre <= 1'b0;
    end
    else begin
        emac_dval_pre <= emac_dval_pre;
    end
end
    //eop/mod 
always @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
        emac_eop_pre <= 1'b0;
        emac_mod_pre <= 5'b0;
    end
    else if (ack == 1'b1) begin
        emac_eop_pre <= 1'b1;
        emac_mod_pre <= emac_len_pre[4:0];
    end
    else begin
        emac_eop_pre <= 1'b0;
        emac_mod_pre <= 5'b0;
    end
end

//输出数据延迟一拍输出
always @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
        emac_data_final     <= 256'b0;
        emac_dval_final     <=   1'b0;
        // emac_dsav_final     <=   1'b0;
        emac_sop_final      <=   1'b0;
        emac_eop_final      <=   1'b0;
        emac_mod_final      <=   5'b0;
        mac_dest_port_out   <=   4'b0;
        emac_len_final      <=  11'b0;
        //emac_pri_final      <=   3'b0;
    end
    else begin
        emac_data_final     <= q                    ;
        emac_dval_final     <= emac_dval_pre        ;
        // emac_dsav_final     <= emac_dsav_pre        ;
        emac_sop_final      <= emac_sop_pre         ;
        emac_eop_final      <= emac_eop_pre         ;
        emac_mod_final      <= emac_mod_pre         ;
        mac_dest_port_out   <= mac_dest_port_out_pre;
        emac_len_final      <= emac_len_pre         ;
        //emac_pri_final      <= emac_pri_pre         ;
    end
end

endmodule
